package RushHour;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;

/**
 * This class contains only a simple main for testing your algorithm on data
 * with one or more heuristics. The main begins by reading in all of the puzzles
 * described in a file named in <tt>argv[0]</tt>. It then proceeds to run A*
 * using each heuristic listed below on each of the puzzles (simply comment out
 * any heuristics you don't want to be testing on). In each case, it prints out
 * the solution path that was computed. Finally, it prints out a summary table
 * of the results. You may wish to modify or replace this <tt>main</tt> in any
 * way that you wish. (However, other classes that we have provided should not
 * be modified directly.)
 */
public class RushHour {

	/**
	 * The main for running all puzzles with all heuristics, and printing out
	 * all solution paths as well as a final summary table.
	 */
	public static void main(String argv[]) throws FileNotFoundException,
			IOException {
		if (argv.length == 0)
		{
			System.out
					.println("USAGE: <Puzzles File> [optional-time per problem] " +
							"[optional - show puzzles] [optional - use simple heuristic");
			return;
		}
		Puzzle[] puzzles = Puzzle.readPuzzlesFromFile(argv[0]);
		if (puzzles == null)
			return;
		long timeoutMilisec = Long.MAX_VALUE;
		if (argv.length >= 2)
			timeoutMilisec = Long.parseLong(argv[1]);
		boolean showPuzzels = false;
		if (argv.length >=3 && argv[2].equals("1"))
			showPuzzels = true;
		boolean useSimpleHeurstic = false;
		if (argv.length >=4 && argv[3].equals("1"))
			useSimpleHeurstic = true;	

		String[] heuristic_names = null;
		int num_puzzles = puzzles.length;
		int num_heuristics = 0;

		int[][] num_expanded = null;
		int[][] soln_depth = null;
		long[][] timesOfOp = null;
		double[][] heuristicAvarage = null;
		int[][] maxDepths = null;
		int[][] minDepths = null;
		double[][] avgDepth = null;

		// run each heuristic on each puzzle
		for (int i = 0; i < num_puzzles; i++) {
			System.out
					.println("=================================================");
			System.out.println("puzzle number " + puzzles[i].getName());

			Heuristic[] heuristics = { new BlockingHeuristic(puzzles[i], useSimpleHeurstic) };

			if (i == 0) {
				num_heuristics = heuristics.length;
				num_expanded = new int[num_puzzles][num_heuristics];
				soln_depth = new int[num_puzzles][num_heuristics];
				timesOfOp = new long[num_puzzles][num_heuristics];
				heuristicAvarage = new double[num_puzzles][num_heuristics];
				maxDepths = new int[num_puzzles][num_heuristics];
				minDepths = new int[num_puzzles][num_heuristics];
				avgDepth = new double[num_puzzles][num_heuristics];

				heuristic_names = new String[num_heuristics];
				for (int h = 0; h < num_heuristics; h++)
					heuristic_names[h] = heuristics[h].getClass().getName();
			}

			for (int h = 0; h < num_heuristics; h++) {
				puzzles[i].resetSearchCount();
				long startTime = System.currentTimeMillis();
				AStar search = new AStar(puzzles[i], heuristics[h],
						timeoutMilisec);
				long endTime = System.currentTimeMillis();

				if (search.path == null) {
					System.out.println("NO SOLUTION FOUND.");
					soln_depth[i][h] = -1;
				} else {

					for (int j = 0; j < search.path.length; j++) {
						search.path[j].print(showPuzzels);
						System.out.print("  ");
						if (j > 0 && j % 9 == 0)
							System.out.println();
					}
					System.out.println();
					num_expanded[i][h] = puzzles[i].getSearchCount();
					soln_depth[i][h] = search.path.length - 1;
					timesOfOp[i][h] = Math.max(1,endTime - startTime);
					heuristicAvarage[i][h] = search.getHeuristicAvarage();
					maxDepths[i][h] = search.getMaxDepth();
					minDepths[i][h] = search.getMinDepth();
					avgDepth[i][h] = search.getAvgDepth();

					System.out.println("nodes expanded: " + num_expanded[i][h]
							+ ", soln depth: " + soln_depth[i][h]);
					System.out.println("time in milisec: " + timesOfOp[i][h]);
				}
			}
			System.out.println();
		}

		// print the results in a table
		System.out.println();

		System.out.print("          ");
		for (int h = 0; h < num_heuristics; h++)
			System.out.print(" |    " + right_pad(heuristic_names[h], 26));
		System.out.println();

		System.out.print("name      ");
		for (int h = 0; h < num_heuristics; h++)
			System.out
					.print(" |    nodes   depth    EBF	time  maxDepth minDepth avgDepth");
		System.out.println();

		System.out.print("----------");
		for (int h = 0; h < num_heuristics; h++)
			System.out.print("-+--------------------------------------------------------");
		System.out.println();

		NumberFormat brfac_nf = new DecimalFormat("##0.000");

		for (int i = 0; i < num_puzzles; i++) {
			System.out.print(right_pad(puzzles[i].getName(), 10));

			for (int h = 0; h < num_heuristics; h++) {
				if (soln_depth[i][h] < 0) {
					System.out.print(" |  ** search failed ** ");
				} else {
					System.out.print(" | "
							+ left_pad(Integer.toString(num_expanded[i][h]), 8)
							+ " "
							+ left_pad(Integer.toString(soln_depth[i][h]), 6)
							+ " "
							+ left_pad(brfac_nf.format(BranchingFactor.compute(
									num_expanded[i][h], soln_depth[i][h])), 8)
							+ " " 
							+ left_pad(Long.toString(timesOfOp[i][h]), 5)
							+ " "
							+ left_pad(Integer.toString(maxDepths[i][h]), 6)
							+ " "
							+ left_pad(Integer.toString(minDepths[i][h]),8)
							+ " "
							+ left_pad(brfac_nf.format(avgDepth[i][h]), 10));

				}
			}
			System.out.println();
		}
	}

	private static String left_pad(String s, int n) {
		for (int i = n - s.length(); i > 0; i--)
			s = " " + s;
		return s;
	}

	private static String right_pad(String s, int n) {
		for (int i = n - s.length(); i > 0; i--)
			s = s + " ";
		return s.substring(0, n);
	}

}
